﻿///
/// Network.fs
/// Defines the most important elements.
///

namespace PathFinder

open System
open System.Collections
open System.Collections.Generic
open Microsoft.FSharp.Math

#light

///
/// Contains the most important elements inside a network.
///
module public Network =
    (* Types *)
    ///
    /// Represents a key for node.
    ///
    type public NodeIdentifier = string
    ///
    /// Represents the cost of a connection.
    ///
    type public ConnectionWeight = double
    ///
    /// Represents the coordinate type.
    ///
    type public NodeCoordinate = float * float
    ///
    /// When memorizing neighbourhood, it is necessary to store the node and the connection weight. 
    ///
    type public NetworkNodeNeighbour = {
        Node: NetworkNode;
        Weight: ConnectionWeight
        }
    ///
    /// Represents a node in the network.
    ///
    and public NetworkNode =
        (* Node name *)
        val private name: NodeIdentifier
        (* Coordinates *)
        val private coordinates: NodeCoordinate
        (* Neighbourhood *)
        val mutable private neighbourhood: NetworkNodeNeighbour list
        (* Name constructor *)
        new (nm: NodeIdentifier) = {
            name = nm;
            coordinates = (0.0, 0.0);
            neighbourhood = [];
            }
        (* Constructor with coordinates *)
        new (nm: NodeIdentifier, coord: NodeCoordinate) = {
            name = nm;
            coordinates = coord;
            neighbourhood = [];
            }
        (* Constructor with coordinates in regular form *)
        new (nm: NodeIdentifier, coordx: float, coordy: float) = {
            name = nm;
            coordinates = (coordx, coordy);
            neighbourhood = [];
            }
        (* Methods *)
        ///
        /// Indexer for neighbourhood.
        /// Integer indexing.
        ///
        member public self.Item
            with get (id: int) = (self.neighbourhood.[abs (id % self.Grade)]).Node
        ///
        /// Indexer for neighbourhood.
        /// String indexing.
        ///
        member public self.Item
            with get (id: string) =
                try
                    let found = List.find (fun (x: NetworkNodeNeighbour) -> x.Node.Name = id) self.neighbourhood
                    found.Node |> Some
                with
                    | :? KeyNotFoundException as ex -> None
        ///
        /// Gets the list of neighbours
        ///
        member public self.Neighbourhood
            with get () =
                self.neighbourhood
        ///
        /// Looking in neighbours, it gets the connection weight of the indexed node.
        ///
        member public self.GetWeightToNeighbour (name: NodeIdentifier) =
            let n = List.find (fun x -> x.Node.Name = name) self.neighbourhood
            n.Weight
        ///
        /// Inserts a new neighbour. Appends it to the list.
        ///
        member public self.AddNeighbour (neigh: NetworkNode, weight: ConnectionWeight) =
            self.neighbourhood <- ({Node = neigh; Weight = weight} :: self.neighbourhood)
        ///
        /// Gets the the grade of the node.
        ///
        member public self.Grade = 
            self.neighbourhood.Length
        ///
        /// Getter for name.
        ///
        member public self.Name
            with get () = self.name
        ///
        /// Getter for coordinates.
        ///
        member public self.Coordinates
            with get () = fst self.coordinates
        ///
        /// Getting X coordinate.
        ///
        member public self.CoordinateX =
            fst self.coordinates
        ///
        /// Getting to node.
        ///
        member public self.CoordinateY =
            snd self.coordinates
        ///
        /// Printing node.
        ///
        override self.ToString () =
            "Node: " + self.name + ". Coordinates: {" + ((fst self.coordinates).ToString ()) + ", " + ((snd self.coordinates).ToString ()) + "}. Grade: " + self.Grade.ToString()
        ///
        /// Comparison.
        ///
        override self.Equals (o: Object) =
            let nnode = unbox<NetworkNode> (o)
            nnode.Name = self.Name
        ///
        /// Hash code.
        ///
        override self.GetHashCode () = 
            1
    ///
    /// Prints a neighbour.
    ///
    let public PrintNeighbour (neigh: NetworkNodeNeighbour) =
        "Neigh node= " + (neigh.Node.ToString ()) + " | Weight = " + (neigh.Weight.ToString ())
    ///
    /// Represents a connection between two nodes.
    ///
    type public NetworkConnection =
        (* Connection name *)
        val private name: string
        (* Starting node *)
        val private nodeFrom: NetworkNode
        (* Ending node *)
        val private nodeTo: NetworkNode
        (* Cost of the connection *)
        val private weight: ConnectionWeight
        (* Basic constructor *)
        new (nodeA: NetworkNode, nodeB: NetworkNode) = {
            name = "Connection";
            nodeFrom = nodeA;
            nodeTo = nodeB;
            weight = 0.0;
            }
        (* Incomplete constructor *)
        new (nm: string, nodeA: NetworkNode, nodeB: NetworkNode) = {
            name = nm;
            nodeFrom = nodeA;
            nodeTo = nodeB;
            weight = 0.0;
            }
        (* Complete constructor *)
        new (nm: string, nodeA: NetworkNode, nodeB: NetworkNode, cost: ConnectionWeight) = {
            name = nm;
            nodeFrom = nodeA;
            nodeTo = nodeB;
            weight = cost;
            }
        (* Methods *)
        ///
        /// Returns the name of the connection.
        ///
        member public self.Name
            with get () = self.name
        ///
        /// Returns the node From.
        ///
        member public self.NodeFrom
            with get () = self.nodeFrom
        ///
        /// Returns the node To.
        ///
        member public self.NodeTo
            with get () = self.nodeTo
        ///
        /// Returns the connection weight.
        ///
        member public self.Weight
            with get () = self.weight
    (* Functions *)
    ///
    /// Returns the distance between two nodes.
    ///
    let public Norm (node1: NetworkNode, node2: NetworkNode) =
        let diffx = node1.CoordinateX - node2.CoordinateX
        let diffy = node1.CoordinateY - node2.CoordinateY
        sqrt ((diffx * diffx) + (diffy * diffy))
